home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 June: Reference Library / Dev.CD Jun 94.toast / Periodicals / develop / develop Issue 18 / develop 18 code / Hierarchical Lists / Src / TwistDownList.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-16  |  39.7 KB  |  1,269 lines  |  [TEXT/KAHL]

  1. /*                                    TwistDownList.c                                */
  2. /*
  3.  * List In A List Sample
  4.  * TwistDownList.c
  5.  * Copyright © 1993-94 Apple Computer Inc.
  6.  *
  7.  * TwistDownList manages all aspects of the "twist-down" list, a one-column text
  8.  * list where each row contains a triangular button. The button may be in one of
  9.  * two states: closed and opened. When opened, sub-elements to this row are
  10.  * displayed. The twist-down list is generally based on code from NewsWatcher
  11.  * by Steve Falkenburg, but Steve says that the code was originally written by
  12.  * John Norstad.
  13.  *
  14.  * Note that you must provide a LDEF stub resource in your application resource
  15.  * file. See ListInAList.r for an example.
  16.  *
  17.  * Because of the way that the callback LDEF is managed, it must be in the
  18.  * application's root segment. If this is inappropriate, the segement resource
  19.  * must be marked "preload" and "locked" -- otherwise, your program will crash.
  20.  */
  21. #include "TwistDownList.h"
  22. #include <Palettes.h>
  23. #include <GestaltEqu.h>
  24. #ifdef __powerc
  25. /*
  26.  * PowerPC System area locations in "Get" and "Set" functions. They are
  27.  * defined in LowMem.h
  28.  */
  29. #include <LowMem.h>
  30. #else
  31. #ifdef MPW
  32. /*
  33.  * MPW requires a reference to a low memory global to set hiliting
  34.  * Think C and MetroWorks includes SysEqu.h in the default MacHeaders file.
  35.  */
  36. #include <SysEqu.h>
  37. #include <Resources.h>
  38. #endif
  39. #endif
  40.  
  41. #ifndef MONOCHROME_FILL
  42. #define MONOCHROME_FILL    0
  43. #endif
  44. /*
  45.  * A common List Selection flag variation that equates Shift and Command keys.
  46.  */
  47. #define SELECTION_FLAGS    \
  48.     (lUseSense | lNoRect | lNoExtend | lNoNilHilite | lDoVAutoscroll)
  49. /*
  50.  * SELECTION_FLAGS, by default, allows a single list cell to be selected.
  51.  */ 
  52. #ifndef SELECTION_FLAGS
  53. #define SELECTION_FLAGS (lOnlyOne | lNoNilHilite | lDoVAutoscroll)
  54. #endif
  55. /*
  56.  * kAnimationDelay is the number of ticks to display the intermediate
  57.  * "twisting" glyph.
  58.  */
  59. #define kAnimationDelay        3L
  60. /*
  61.  * The triangle gap parameters define the amount of space to display to the
  62.  * left and right of the twist-down triangle. The "outside gap" is to the left
  63.  * on Roman-alphabet scripts and on the right on Hebrew or Arabic scripts.
  64.  * The default values are suitable for small font sizes, but could be increased
  65.  * for large sizes.
  66.  */
  67. #define kTriangleOutsideGap        (1)                    /* From margin to button    */
  68. #define kTriangleInsideGap        (2)                    /* From button to text        */
  69. #define kScrollBarWidth            (16)                /* Width of a scroll bar    */
  70.  
  71. /*
  72.  * The List's userHandle contains our private context information. The PolyHandles
  73.  * are used to draw the "triangle" buttons. Note that they are drawn to the list
  74.  * cell height. When it changes, the triangles will be reconstructed. The fontSize,
  75.  * fontNumber, and isLeftJustify variables are used to draw the list cell content.
  76.  */
  77. struct TwistDownPrivateRecord {
  78.         TwistDownDrawProc    drawProc;                /* This draws the cell data    */
  79.         PolyHandle            openTriangle;            /* The "expanded" button    */
  80.         PolyHandle            closedTriangle;            /* The "closed" button        */
  81.         PolyHandle            intermediateTriangle;    /* The "expanding" button    */
  82.         short                tabIndent;                /* NewTwistDownList param    */
  83.         short                fontSize;                /* for TextSize                */
  84.         short                fontNumber;                /* for TextFont                */
  85.         Boolean                canHiliteSelection;        /* TRUE if hilite ok        */
  86.         Boolean                isLeftJustify;            /* GetSysJust value            */
  87.         short                triangleWidth;            /* Twist-down button width    */
  88. };
  89. typedef struct TwistDownPrivateRecord    TwistDownPrivateRecord,
  90.         *TwistDownPrivatePtr, **TwistDownPrivateHdl;
  91.  
  92. /*
  93.  * The de-referenced private data record is too long to type each time it appears
  94.  * so it will be defined by the PRIVATE macro.
  95.  */
  96. #define PRIVATE            (**((TwistDownPrivateHdl) ((**theList).userHandle)))
  97. /*
  98.  * These macros simplify access to the flag word in the list element.
  99.  */
  100. #define SetTDFlag(elementHdl, mask)        ((**elementHdl).flag |= (mask))
  101. #define ClearTDFlag(elementHdl, mask)    ((**elementHdl).flag &= ~(mask))
  102. #define InvertTDFlag(elementHdl, mask)    ((**elementHdl).flag ^= (mask))
  103. #define TestTDFlag(elementHdl, mask)    (((**elementHdl).flag & (mask)) != 0)
  104. /*
  105.  * The proper way to build a compiled-in LDEF is to plug a transfer address into a
  106.  * stub code resource. The StubRecord must track any changes in the LDEF resource.
  107.  * The #pragma statements are defined only for Power PC: they give errors on
  108.  * Think C. 
  109.  */
  110. #ifdef __powerc
  111. #pragma options align=mac68k
  112. #endif
  113. struct StubRecord {
  114.     long            lea;            /*    Lea            (pc)+8,a0                    */
  115.     short            movea;            /*    Movea.l        (a0),a0                        */
  116.     short            jmp;            /*    jmp            (a0)                        */
  117.     ProcPtr            ldefAddress;    /*    dc.l        0                            */
  118. };
  119. typedef struct StubRecord    **StubHandle;
  120. #ifdef __powerc
  121. #pragma options align=reset
  122. #endif
  123.  
  124. /*
  125.  * Local (private) functions.
  126.  */
  127. /*
  128.  * Dispose of a PolyHandle. This must be a macro. The argument
  129.  * may not have side-effects.
  130.  */
  131. #define ForgetPoly(thePoly) do {        \
  132.         if (thePoly != NULL) {             \
  133.             KillPoly(thePoly);            \
  134.             thePoly = NULL;                \
  135.         }                                \
  136.     } while (0)
  137. #define height(r)        ((r).bottom - (r).top)
  138. #define width(r)        ((r).right - (r).left)
  139.  
  140. static short                    CountVisibleElements(
  141.         TwistDownHdl                twistDownHandle
  142.     );
  143. static void                     ClearSelectedElementBit(
  144.         TwistDownHdl                twistDownHandle
  145.     );
  146. static void                        CopySelectionStateToList(
  147.         ListHandle                    theList,
  148.         short                        selectedRow
  149.     );
  150. static void                        SetElementsInList(
  151.         ListHandle                    theList,
  152.         TwistDownHdl                twistDownHandle,
  153.         Cell                        *currentCell
  154.     );
  155. static pascal void                TwistDownLDEF(
  156.         short                        listMessage,
  157.         Boolean                        listSelect,
  158.         Rect                        *listRect,
  159.         Cell                        listCell,
  160.         short                        listDataOffset,
  161.         short                        listDataLen,
  162.         ListHandle                    listHandle
  163.     );
  164. static void                        DrawTriangle(
  165.         PolyHandle                    polyHandle,
  166.         Point                        polyPoint,
  167.         Boolean                        isSelected
  168.     );
  169.  
  170. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  171.  * NewTwistDownList
  172.  *
  173.  * Create a twist-down list. Before calling, you must set the port to the current
  174.  * window and specify the font and font size that is to be used to draw the list.
  175.  * NewTwistDownList creates an empty one-column list with a vertical scroll bar
  176.  * and no grow box. Only one item may be selected at a time; but this could
  177.  * be changed by the application without difficulty.
  178.  *
  179.  * Note: unlike LNew, the viewRect includes the vertical scroll bar. The frame
  180.  * and focus rectangles should be drawn outside of the list's rView.
  181.  *
  182.  * The tabIndent parameter should be set to the amount to indent successive levels
  183.  * (zero means no indentation). Setting it to the widMax value from the current
  184.  * font seems reasonable.
  185.  *
  186.  * canHiliteSelection should be TRUE for normal selection (the selection is
  187.  * hilited). It should be FALSE if you want to supress selection. Because
  188.  * hirearchical lists often "select" by revealing a sub-topic, this may be
  189.  * more reasonable in many cases.
  190.  *
  191.  * isLeftJustify should be set TRUE for systems using the Roman alphabet. It would
  192.  * be set FALSE for right-to-left languages such as Arabic and Hebrew. It is used
  193.  * to configure the direction of the buttons and the location of text within
  194.  * the displayed list cell.
  195.  */
  196. ListHandle
  197. NewTwistDownList(
  198.         const Rect                    *viewRect,
  199.         TwistDownDrawProc            drawProc,
  200.         unsigned short                tabIndent,
  201.         Boolean                        canHiliteSelection,
  202.         Boolean                        isLeftJustify
  203.     )
  204. {
  205.         register TwistDownPrivatePtr    privatePtr;
  206.         ListHandle                        theList;
  207.         short                            listHeight;
  208.         Point                            cellSize;
  209.         Rect                            dataBounds;
  210.         Rect                            listRect;
  211.         FontInfo                        info;
  212.         short                            listFontHeight;
  213.         GrafPtr                            currentPort;
  214.         ProcPtr                            listDefProc;
  215.         StubHandle                        stubHandle;
  216.  
  217.         theList = NULL;
  218.         GetPort(¤tPort);
  219.         GetFontInfo(&info);
  220.         listFontHeight = info.ascent + info.descent + info.leading;
  221.         /*
  222.          * Define the list drawing area. If the list viewRect.bottom
  223.          * is less than the portRect.bottom, adjust the list area height
  224.          * integral number of rows will be drawn. If equal, the list
  225.          * area abuts the bottom of the display window and we shouldn't
  226.          * change the bottom or the scroll bars will look wierd.
  227.          */
  228.         listRect = *viewRect;
  229.         listRect.right -= kScrollBarWidth;
  230.         SetPt(&cellSize, width(listRect), listFontHeight);
  231.         if (listRect.bottom < currentPort->portRect.bottom) {
  232.             listHeight = height(listRect);
  233.             listHeight -= (listHeight % listFontHeight);
  234.             listRect.bottom = listRect.top + listHeight;
  235.         }
  236.         /*
  237.          * Define a one-column list.
  238.          */
  239. #ifdef __powerc
  240.         /*
  241.          * Create a universal routine descriptor for our private LDEF. This is
  242.          * the general model for all pointer-to-function references.
  243.          */
  244.         listDefProc = (ProcPtr) NewListDefProc(TwistDownLDEF);
  245. #else
  246.         listDefProc = (ProcPtr) TwistDownLDEF;
  247. #endif
  248.         stubHandle = (StubHandle) GetResource('LDEF', LDEF_Stub);
  249.         if (stubHandle == NULL)
  250.             goto exit;                    /* Failure                                */
  251.         (**stubHandle).ldefAddress = listDefProc;
  252.         SetRect(&dataBounds, 0, 0, 1, 0);
  253.         theList = LNew(
  254.                 &listRect,                /* Viewing area                            */
  255.                 &dataBounds,            /* Rows and col's                        */
  256.                 cellSize,                /* Element size                            */
  257.                 LDEF_Stub,                /* Callback defproc                        */
  258.                 currentPort,            /* Display window                        */
  259.                 TRUE,                    /* Draw it                                */
  260.                 FALSE,                    /* No grow box                            */
  261.                 FALSE,                    /* No horiz scroll                        */
  262.                 TRUE                    /* Vertical scroll                        */
  263.             );
  264.         if (theList == NULL)
  265.             goto exit;
  266.         (**theList).selFlags = SELECTION_FLAGS;
  267.         (**theList).refCon = 0;            /* Paranoia                                */
  268.         (**theList).userHandle = NewHandleClear(sizeof (TwistDownPrivateRecord));
  269.         if ((**theList).userHandle == NULL)
  270.             goto failure;
  271.         privatePtr = (TwistDownPrivatePtr) (*(**theList).userHandle);
  272. #define PRIV (*privatePtr)
  273.         PRIV.drawProc = drawProc;
  274.         PRIV.tabIndent = tabIndent;
  275.         PRIV.canHiliteSelection = canHiliteSelection;
  276.         PRIV.isLeftJustify = isLeftJustify;
  277.         PRIV.fontNumber = currentPort->txFont;
  278.         PRIV.fontSize = currentPort->txSize;
  279. #undef PRIV
  280.         CreateTwistDownButtons(theList);
  281.         goto exit;
  282. failure:
  283.         DisposeTwistDownList(theList);
  284.         theList = NULL;
  285. exit:    return (theList);
  286. }
  287.  
  288. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  289.  * DisposeTwistDownList
  290.  *
  291.  * Dispose of the list and our private data.
  292.  */
  293. void
  294. DisposeTwistDownList(
  295.         ListHandle                    theList
  296.     )
  297. {
  298.         if (theList != NULL) {
  299.             if ((**theList).userHandle != NULL) {
  300.                 ForgetPoly(PRIVATE.openTriangle);
  301.                 ForgetPoly(PRIVATE.closedTriangle);
  302.                 ForgetPoly(PRIVATE.intermediateTriangle);
  303.                 DisposeHandle((Handle) (**theList).userHandle);
  304.                 (**theList).userHandle = NULL;
  305.             }
  306.             LDispose(theList);
  307.         }
  308. }
  309.  
  310. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  311.  * CreateTwistDownButtons
  312.  *
  313.  * CreateTwistDownButtons
  314.  * This function creates the three states of the twist-down button that are drawn
  315.  * in the display list.
  316.  *
  317.  * CreateTwistDownButtons is called when the list is created. The application must
  318.  * call it directly after changing the list cell height (by calling LSize).
  319.  *
  320.  * Note that the port and text drawing characteristics must have been set.
  321.  */
  322. void
  323. CreateTwistDownButtons(
  324.         ListHandle                    theList
  325.     )
  326. {
  327.         short                        buttonSize;
  328.         short                        halfSize;
  329.         short                        intermediateSize;
  330.         FontInfo                    info;
  331.  
  332.         ForgetPoly(PRIVATE.openTriangle);
  333.         ForgetPoly(PRIVATE.closedTriangle);
  334.         ForgetPoly(PRIVATE.intermediateTriangle);
  335.         GetFontInfo(&info);
  336.         buttonSize = info.ascent;                /* The "show sublist" button    */
  337.         buttonSize &= ~1;                        /* Round down to an even number    */
  338.         halfSize = buttonSize / 2;
  339.         intermediateSize = (buttonSize * 3) / 4;
  340.         PRIVATE.openTriangle = OpenPoly();
  341.             MoveTo(0, halfSize);
  342.             LineTo(buttonSize, halfSize);
  343.             LineTo(halfSize, buttonSize);
  344.             LineTo(0, halfSize);
  345.         ClosePoly();
  346.         if (PRIVATE.isLeftJustify) {            /* Roman alphabet triangles        */
  347.             PRIVATE.closedTriangle = OpenPoly();
  348.                 MoveTo(halfSize, 0);
  349.                 LineTo(buttonSize, halfSize);
  350.                 LineTo(halfSize, buttonSize);
  351.                 LineTo(halfSize, 0);
  352.             ClosePoly();
  353.             PRIVATE.intermediateTriangle = OpenPoly();    
  354.                 MoveTo(intermediateSize, 0);
  355.                 LineTo(intermediateSize, intermediateSize);
  356.                 LineTo(0, intermediateSize);
  357.                 LineTo(intermediateSize, 0);
  358.             ClosePoly();
  359.         }
  360.         else {                                    /* Arabic/Hebrew triangles        */
  361.             PRIVATE.closedTriangle = OpenPoly();
  362.                 MoveTo(buttonSize - halfSize, 0);
  363.                 LineTo(0, halfSize);
  364.                 LineTo(buttonSize - halfSize, buttonSize);
  365.                 LineTo(buttonSize - halfSize, 0);
  366.             ClosePoly();
  367.             PRIVATE.intermediateTriangle = OpenPoly();
  368.                 MoveTo(buttonSize - intermediateSize, 0);
  369.                 LineTo(buttonSize - intermediateSize, intermediateSize);
  370.                 LineTo(buttonSize, intermediateSize);
  371.                 LineTo(buttonSize - intermediateSize, 0);
  372.             ClosePoly();
  373.         }        
  374.         /*
  375.          * Remember the width of the "button" area.
  376.          */
  377.         PRIVATE.triangleWidth =
  378.                     (**PRIVATE.openTriangle).polyBBox.right
  379.                     + kTriangleOutsideGap
  380.                     + kTriangleInsideGap;
  381. }
  382.  
  383. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  384.  * DoTwistDownClick
  385.  *
  386.  * DoTwistDownClick handles all processing after a click in the list window.
  387.  * It returns an indication of the user action:
  388.  *    kTwistDownNotInList
  389.  *        The click was not in this list. Your application may ignore this click or
  390.  *        take other appropriate action.
  391.  *    kTwistDownNoClick
  392.  *        The user released the mouse outside of the list area: this click should be
  393.  *        ignored. (It may have been a click in the scroll bar.)
  394.  *    kTwistDownButtonClick
  395.  *        The user clicked on the twist-down button. The list will be expanded or
  396.  *        contracted as appropriate.
  397.  *    kTwistDownClick
  398.  *        The user clicked (once) on a list datum. The application should treat this
  399.  *        as an item selection.
  400.  *    kTwistDownDoubleClick
  401.  *        The user double-clicked on a list datum. The application should open this
  402.  *        item or take other appropriate action.
  403.  *
  404.  * If DoTwistDownClick returns kTwistDownButtonClick, kTwistDownClick, or
  405.  * kTwistDownDoubleClick, selectedListCell will be set to the cell that the user
  406.  * clicked on.
  407.  */
  408. TwistDownClickState
  409. DoTwistDownClick(
  410.         ListHandle                    theList,
  411.         const EventRecord            *eventRecordPtr,
  412.         Cell                        *selectedListCell
  413.     )
  414. {
  415.         Cell                        theCell;
  416.         Rect                        hitRect;
  417.         Boolean                        inHitRect;
  418.         Boolean                        newInHitRect;
  419.         short                        cellHeight;
  420.         short                        visibleTop;
  421.         TwistDownHdl                twistDownHandle;
  422.         Point                        mousePt;
  423.         TwistDownClickState            result;
  424.         long                        finalTicks;
  425.  
  426.         mousePt = eventRecordPtr->where;
  427.         GlobalToLocal(&mousePt);
  428.         hitRect = (**theList).rView;
  429.         hitRect.right += kScrollBarWidth;
  430.         if (PtInRect(mousePt, &hitRect) == FALSE)
  431.             result = kTwistDownNotInList;
  432.         else {
  433.             /*
  434.              * Set hitRect to the area of the list that contains the twist-down
  435.              * triangle button. Note that this presumes a list where only column
  436.              * zero is displayed.
  437.              */
  438.             if (PRIVATE.isLeftJustify) {
  439.                 hitRect.right = (**theList).rView.left
  440.                         + (**theList).indent.h
  441.                         + PRIVATE.triangleWidth;
  442.             }
  443.             else {
  444.                 hitRect.left = (**theList).rView.right
  445.                         - (**theList).indent.h
  446.                         - PRIVATE.triangleWidth;
  447.             }
  448.             inHitRect = FALSE;
  449.             if (PtInRect(mousePt, &hitRect)) {
  450.                 /*
  451.                  * It's in a the triangle area. Find the selected cell and check
  452.                  * whether this cell's element has a visible twist-down button.
  453.                  */
  454.                 visibleTop = (**theList).visible.top;
  455.                 cellHeight = (**theList).cellSize.v;
  456.                 theCell.h = 0;        /* This has the visual content            */
  457.                 theCell.v =
  458.                     ((mousePt.v - (**theList).rView.top) / cellHeight)
  459.                     + visibleTop;
  460.                 /*
  461.                  * Set inHitRect TRUE if there is a sub-list button here.
  462.                  * Note: it is possible to have a button but no actual sub-list
  463.                  * (consider an empty folder in a disk hierarchy: the presence
  464.                  * of the button indicates "this is a folder" to the user).
  465.                  */
  466.                 twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  467.                 if (twistDownHandle != NULL
  468.                  && TestTDFlag(twistDownHandle, kHasTwistDown))
  469.                     inHitRect = TRUE;
  470.             }
  471.             if (inHitRect == FALSE) {
  472.                 /*
  473.                  * This cell doesn't have an expansion triangle, or the user did
  474.                  * not click in the button area. Just call the  normal list click
  475.                  * handler to manage the scroll bars. Set result appropriately.
  476.                  */
  477.                 result = (LClick(mousePt, eventRecordPtr->modifiers, theList))
  478.                     ? kTwistDownDoubleClick
  479.                     : kTwistDownClick;
  480.             }
  481.             else {
  482.                 /*
  483.                  * The user clicked on in the twist-down button area. Simulate a
  484.                  * button click and track the mouse as it wanders in and out of the
  485.                  * button area. (inHitRect is true at this point). Whenever the
  486.                  * button selection state changes, call LDraw to redraw the
  487.                  * twist-down triangle. This is the way to simulate TrackControl.
  488.                  */
  489.                 SetTDFlag(twistDownHandle,
  490.                     (kDrawButtonFilled | kOnlyRedrawButton));
  491.                 LDraw(theCell, theList);
  492.                 /*
  493.                  * Set hitRect to the dimensions of the twist-down button.
  494.                  */
  495.                 hitRect.top =
  496.                     ((theCell.v - visibleTop) * cellHeight)
  497.                     + (**theList).rView.top;
  498.                 hitRect.bottom = hitRect.top + cellHeight;
  499.                 /*
  500.                  * Track the mouse while it still down: if it moves into the
  501.                  * triangle rectangle, redraw it filled, if it moves out of the
  502.                  * triangle, redraw it unfilled.
  503.                  */
  504.                 if (StillDown()) {
  505.                     while (WaitMouseUp()) {
  506.                         GetMouse(&mousePt);
  507.                         newInHitRect = PtInRect(mousePt, &hitRect);
  508.                         if (newInHitRect != inHitRect) {
  509.                             /*
  510.                              * The mouse moved in or out of the triangle.
  511.                              */
  512.                             InvertTDFlag(twistDownHandle, kDrawButtonFilled);
  513.                             LDraw(theCell, theList);
  514.                             inHitRect = newInHitRect;
  515.                         }
  516.                     }
  517.                 }
  518.                 /*
  519.                  * The user released the mouse.
  520.                  */
  521.                 if (inHitRect == FALSE) {
  522.                     /*
  523.                      * Normally, drawButtonFilled will be clear. It can be set,
  524.                      * however, if the user clicks so briefly on the triangle
  525.                      * that the StillDown() test above is FALSE.
  526.                      */
  527.                     if (TestTDFlag(twistDownHandle, kDrawButtonFilled)) {
  528.                         ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  529.                         LDraw(theCell, theList);
  530.                     }
  531.                     result = kTwistDownNoClick;
  532.                 }
  533.                 else {
  534.                     /*
  535.                      * The user released the mouse in the expansion triangle.
  536.                      * Draw an intermediate "animation" triangle. Then call 
  537.                      * ExpandOrCollapseTwistDownList which will redraw the
  538.                      * button in its new state.
  539.                      */
  540.                     SetTDFlag(twistDownHandle,
  541.                         (kDrawIntermediate | kEraseButtonArea));
  542.                     LDraw(theCell, theList);
  543.                     Delay(kAnimationDelay, &finalTicks);
  544.                     ClearTDFlag(twistDownHandle,
  545.                         (kDrawIntermediate | kDrawButtonFilled | kEraseButtonArea));
  546.                     ExpandOrCollapseTwistDownList(theList, theCell);
  547.                     result = kTwistDownButtonClick;
  548.                     *selectedListCell = theCell;
  549.                 }
  550.                 ClearTDFlag(twistDownHandle, kOnlyRedrawButton);
  551.             }
  552.         }
  553.         return (result);
  554. }
  555.  
  556. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  557.  * ExpandOrCollapseTwistDownList
  558.  *
  559.  * ExpandOrCollapseTwistDownList modifies the "show sublist" flag for the selected
  560.  * cell and rebuilds the visual display.
  561.  */
  562. void
  563. ExpandOrCollapseTwistDownList(
  564.         ListHandle                    theList,
  565.         Cell                        selectedListCell
  566.     )
  567. {
  568.         TwistDownHdl                twistDownHandle;
  569.         
  570.         twistDownHandle = GetTwistDownElementHandle(theList, selectedListCell);
  571.         if (twistDownHandle != NULL
  572.          && TestTDFlag(twistDownHandle, kHasTwistDown)) {
  573.             InvertTDFlag(twistDownHandle, kShowSublist);
  574.             /*
  575.              * Redraw the twist-down button in its new state.
  576.              */
  577.             ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  578.             SetTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  579.             LDraw(selectedListCell, theList);
  580.             ClearTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  581.             /*
  582.              * If some other part of the list will change, rebuild the
  583.              * List Manager list cells and redraw the list.
  584.              */
  585.             if ((**twistDownHandle).subElement != NULL)
  586.                 BuildVisibleList(theList, selectedListCell.v);
  587.         }
  588. }
  589.  
  590. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  591.  * CreateVisibleList
  592.  *
  593.  * CreateVisibleList is called when the list is created. It stores the list
  594.  * head into cell [0, 0] and calls BuildVisibleList to instantiate the display.
  595.  */
  596. void
  597. CreateVisibleList(
  598.         ListHandle                    theList,
  599.         TwistDownHdl                twistDownHandle
  600.     )
  601. {
  602.         Cell                        theCell;
  603.         
  604.         if ((**theList).dataBounds.bottom == 0) {
  605.             /*
  606.              * Add one row to the list so there is a place for the head element.
  607.              */
  608.             LDoDraw(FALSE, theList);
  609.             LAddRow(1, 0, theList);
  610.             LDoDraw(TRUE, theList);
  611.         }
  612.         SetPt(&theCell, 0, 0);
  613.         LSetCell(&twistDownHandle, sizeof twistDownHandle, theCell, theList);
  614.         BuildVisibleList(theList, 0);
  615. }
  616.  
  617. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  618.  * BuildVisibleList
  619.  *
  620.  * BuildVisibleList is called when the list is created, or when the user clicks on
  621.  * a twist-down button. selectedRow is the first row that needs to be redrawn.
  622.  * To rebuild the entire list, store the list head into cell [0, 0] and call
  623.  * with selectedRow = 0. Note: because people can be rebuilding the list from
  624.  * within a larger sublist context, the list head must be stored in cell [0, 0].
  625.  */
  626. void
  627. BuildVisibleList(
  628.         ListHandle                    theList,
  629.         short                        selectedRow
  630.     )
  631. {
  632.         short                        nRows;            /* How many we need    to show    */
  633.         short                        currentRows;    /* How many are in the list    */
  634.         Rect                        viewRect;
  635.         TwistDownHdl                listHead;
  636.         Cell                        theCell;
  637.         
  638.         LDoDraw(FALSE, theList);
  639.         SetPt(&theCell, 0, 0);                        /* Get the list head        */
  640.         listHead = GetTwistDownElementHandle(theList, theCell);
  641.         ClearSelectedElementBit(listHead);
  642.         CopySelectionStateToList(theList, selectedRow);
  643.         nRows = CountVisibleElements(listHead);
  644.         currentRows = (**theList).dataBounds.bottom;
  645.         if (currentRows > nRows)
  646.             LDelRow(currentRows - nRows, nRows, theList);    /* Shrink the list    */
  647.         else if (currentRows < nRows)
  648.             LAddRow(nRows - currentRows, currentRows + 1, theList);    /* Grow it    */
  649.         if (nRows != 0) {
  650.             SetElementsInList(theList, listHead, &theCell);
  651.         }
  652.         LDoDraw(TRUE, theList);
  653.         /*
  654.          * Redraw any elements that are greater than the inserted row.
  655.          */
  656.         viewRect = (**theList).rView;
  657.         viewRect.top += ((selectedRow + 1) - (**theList).visible.top)
  658.                             * (**theList).cellSize.v;
  659.         if (viewRect.top < viewRect.bottom)
  660.             InvalRect(&viewRect);
  661. }
  662.  
  663. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  664.  * CountVisibleElements
  665.  *
  666.  * CountVisibleElements is a recursive function that returns the number of visible
  667.  * elements in the list. Call with the list head to process the entire list. Note:
  668.  * list elements are visible, but sub-lists are visible only if the button state
  669.  * requests visiblity. This function may be called with a NULL argument without
  670.  * problems. Also note that we ignore the current List Manager cell data, working
  671.  * only with the actual linked list structure. By convention, however, the list
  672.  * head is stored in cell [0, 0].
  673.  */
  674. static short
  675. CountVisibleElements(
  676.         TwistDownHdl                twistDownHandle
  677.     )
  678. {
  679.         short                        result;
  680.  
  681.         result = 0;
  682.         while (twistDownHandle != NULL) {
  683.             ++result;
  684.             if (TestTDFlag(twistDownHandle, kShowSublist))
  685.                 result += CountVisibleElements((**twistDownHandle).subElement);
  686.             twistDownHandle = (**twistDownHandle).nextElement;
  687.         }            
  688.         return (result);
  689. }
  690.  
  691. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  692.  * CountListElements
  693.  *
  694.  * CountListElements returns the number of elements in the list (it counts all
  695.  * elements, visible or not.
  696.  */
  697. unsigned long
  698. CountListElements(
  699.         TwistDownHdl                twistDownHandle
  700.     )
  701. {
  702.         unsigned long                result;
  703.  
  704.         result = 0;
  705.         while (twistDownHandle != NULL) {
  706.             ++result;
  707.             if ((**twistDownHandle).subElement != NULL)
  708.                 result += CountVisibleElements((**twistDownHandle).subElement);
  709.             twistDownHandle = (**twistDownHandle).nextElement;
  710.         }            
  711.         return (result);
  712. }
  713.  
  714.  
  715. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  716.  * ClearSelectedElementBit
  717.  *
  718.  * When we expand or contract the visible list, the relationship between the
  719.  * visual display and the actual data changes. In particular, the selected cell
  720.  * may contain different data. To resolve this dilemna, we copy the current
  721.  * selection from the display list to the data elements, then copy the information
  722.  * back when the new elements are inserted into the list. This is a three-step
  723.  * process. First, we clear out the kSelectedElement bit from the linked list.
  724.  */
  725. static void
  726. ClearSelectedElementBit(
  727.         TwistDownHdl                twistDownHandle
  728.     )
  729. {
  730.         while (twistDownHandle != NULL) {
  731.             ClearTDFlag(twistDownHandle, kSelectedElement);
  732.             if ((**twistDownHandle).subElement != NULL)
  733.                 ClearSelectedElementBit((**twistDownHandle).subElement);
  734.             twistDownHandle = (**twistDownHandle).nextElement;
  735.         }            
  736. }
  737.  
  738. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  739.  * ClearSelectedElementBit
  740.  *
  741.  * After clearing the kSelectedElementBit from the element list, we save the
  742.  * "is selected" bit from the display list into the associated element. Note that
  743.  * the cell will be deselected but selection state is restored when the list is
  744.  * rebuilt. The function starts with the row after the selected (clicked on) cell
  745.  * as that cell is not redrawn and, presumably, is correctly hilited.
  746.  */
  747. static void
  748. CopySelectionStateToList(
  749.         ListHandle                    theList,
  750.         short                        selectedRow
  751.     )
  752. {
  753.         TwistDownHdl                twistDownHandle;
  754.         Cell                        theCell;
  755.         
  756.         SetPt(&theCell, 0, selectedRow + 1);
  757.         while (LGetSelect(TRUE, &theCell, theList)) {
  758.             twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  759.             if (twistDownHandle != NULL)
  760.                 SetTDFlag(twistDownHandle, kSelectedElement);
  761.             LSetSelect(FALSE, theCell, theList);
  762.             ++theCell.v;
  763.         }
  764. }
  765.  
  766. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  767.  * SetElementsInList
  768.  *
  769.  * SetElementsInList is a recursive function that copies visible list elements from
  770.  * the linked list to the List Manager list. The List Manager list was extended so
  771.  * it holds all visible cells. Note that currentCell is a "global" that always
  772.  * contains the current List Manager cell. To process the entire list, set
  773.  * currentCell to [0, 0] and call with the list head. If the list element is
  774.  * selected (from CopySelectionStateToList above), select this list cell.
  775.  */ 
  776. static void
  777. SetElementsInList(
  778.         ListHandle                    theList,
  779.         TwistDownHdl                twistDownHandle,
  780.         Cell                        *currentCell
  781.     )
  782. {
  783.         while (twistDownHandle != NULL) {
  784.             LSetCell(
  785.                 &twistDownHandle, sizeof twistDownHandle, *currentCell, theList);
  786.             if (TestTDFlag(twistDownHandle, kSelectedElement))
  787.                 LSetSelect(TRUE, *currentCell, theList);
  788.             ++currentCell->v;
  789.             if (TestTDFlag(twistDownHandle, kShowSublist)) {
  790.                 SetElementsInList(
  791.                     theList,
  792.                     (**twistDownHandle).subElement,
  793.                     currentCell
  794.                 );
  795.             }
  796.             twistDownHandle = (**twistDownHandle).nextElement;
  797.         }
  798. }
  799.  
  800. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  801.  * GetTwistDownElementHandle
  802.  *
  803.  * GetTwistDownElementHandle returns the TwistDownHdl that is stored in a List
  804.  * cell. It will return NULL if the cell is out of bounds.
  805.  */
  806. TwistDownHdl
  807. GetTwistDownElementHandle(
  808.         ListHandle                    theList,
  809.         Cell                        theCell
  810.     )
  811. {
  812.         TwistDownHdl                twistDownHandle;
  813.         short                        dataSize;
  814.         
  815.         dataSize = sizeof twistDownHandle;
  816.         LGetCell(&twistDownHandle, &dataSize, theCell, theList);
  817.         if (dataSize != sizeof twistDownHandle)
  818.             twistDownHandle = NULL;
  819.         return (twistDownHandle);
  820. }
  821.  
  822. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  823.  * MakeTwistDownElement
  824.  *
  825.  * MakeTwistDownElement adds an element to a linked list. It is designed to create
  826.  * elements in a hierarchical linked-list where each element may be followed by a
  827.  * successor (on the same "level") and/or by a child list (on a lower "level").
  828.  * Note that MakeTwistDownElement is only concerned with the linked list. It
  829.  * does not change the List Manager list.
  830.  *
  831.  * The parameters are as follows:
  832.  *    previousElement
  833.  *        This is a handle to the predecessor to this element. previousElement is
  834.  *        NULL If this is the first element in a list or the first on a level.
  835.  *    indentLevel
  836.  *        This is the indentation-level of this list element. It is only
  837.  *        used to tab the list elements on the visual display. If you don't
  838.  *        want tabbing, set tabIndent to zero when the list was created.
  839.  *    dataLength
  840.  *        This is the length of the list element datum.
  841.  *    dataPtr
  842.  *        This is a pointer to the first byte of the list element datum. If NULL,
  843.  *        a data block of the requisite size will be created, but the caller is
  844.  *        responsible for filling it in.
  845.  *    result
  846.  *        If MakeElement succeeds, result will will contain a handle to the
  847.  *        list element it created. This is needed to create a successor
  848.  *        element.
  849.  */
  850. OSErr
  851. MakeTwistDownElement(
  852.         TwistDownHdl                previousElement,
  853.         short                        indentLevel,
  854.         unsigned short                dataLength,
  855.         Ptr                            dataPtr,
  856.         TwistDownHdl                *result
  857.     )
  858. {
  859.         TwistDownHdl                twistDownHandle;
  860.         
  861.         twistDownHandle = (TwistDownHdl) NewHandle(
  862.                             sizeof (TwistDownRecord)
  863.                             - sizeof (unsigned char)
  864.                             + dataLength
  865.                         );
  866.         if (twistDownHandle != NULL) {
  867.             if (previousElement != NULL)
  868.                 (**previousElement).nextElement = twistDownHandle;
  869.             (**twistDownHandle).nextElement = NULL;
  870.             (**twistDownHandle).subElement = NULL;
  871.             (**twistDownHandle).flag = 0;
  872.             (**twistDownHandle).indentLevel = indentLevel;
  873.             (**twistDownHandle).dataLength    = dataLength;
  874.             if (dataPtr != NULL)
  875.                 BlockMove(dataPtr, (**twistDownHandle).data, dataLength);
  876.             *result = twistDownHandle;
  877.         }
  878.         return (MemError());
  879. }
  880.  
  881. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  882.  * DisposeTwistDownHdl
  883.  *
  884.  * DisposeTwistDownHdl disposes of the linked list that has the argument at
  885.  * its head, and of all sublists linked to this list. Note that it presumes
  886.  * that the list element does not, itself, contain Ptr or Handle data that
  887.  * must be disposed. (C++ overloading would be useful here.)
  888.  */
  889. void
  890. DisposeTwistDownHdl(
  891.         TwistDownHdl                twistDownHandle,
  892.         DisposeTwistDownCallback    userProc,
  893.         void                        *userData
  894.     )
  895. {
  896.         TwistDownHdl                nextElement;
  897.         TwistDownHdl                subElement;
  898.         
  899.         while (twistDownHandle != NULL) {
  900.             nextElement = (**twistDownHandle).nextElement;
  901.             subElement = (**twistDownHandle).subElement;
  902.             if (userProc != NULL)
  903.                 (*userProc)(twistDownHandle, userData);
  904.             DisposeHandle((Handle) twistDownHandle);
  905.             if (subElement != NULL)
  906.                 DisposeTwistDownHdl(subElement, userProc, userData);
  907.             twistDownHandle = nextElement;
  908.         }
  909. }
  910.  
  911. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  912.  * TwistDownLDEF
  913.  *
  914.  * Draw the twist-down list cell. Note that we have to draw the expansion button
  915.  * in one of five states: (expanded/compressed), (normal/selected), and an
  916.  * animation state.
  917.  */
  918. pascal void
  919. TwistDownLDEF(
  920.         short                            listMessage,
  921.         Boolean                            listSelect,
  922.         Rect                            *listRect,
  923.         Cell                            listCell,
  924.         short                            listDataOffset,
  925.         short                            listDataLen,
  926.         ListHandle                        theList
  927.     )
  928. {
  929. #pragma unused (listCell, listDataOffset)
  930.  
  931.         short                            indent;
  932.         TwistDownHdl                    twistDownHandle;
  933.         register TwistDownPtr            twistDownPtr;
  934.         GrafPtr                            grafPtr;
  935.         short                            saveFontNumber;
  936.         short                            saveFontSize;
  937.         short                            cellSize;
  938.         PolyHandle                        polyHandle;
  939.         Point                            polyPoint;
  940.         Rect                            viewRect;
  941.         signed char                        elementLockState;
  942.         Boolean                            onlyRedrawButton;
  943.         FontInfo                        info;
  944. #define TestFlag(flagBit)    (((*twistDownPtr).flag & (flagBit)) != 0)
  945.  
  946.         switch (listMessage) {
  947.         case lInitMsg:
  948.             /*
  949.              * Initialize the list indentation values. Since the userHandle
  950.              * (which has the TwistDown private data) hasn't been setup yet,
  951.              * we let the current font and font size establish the indentation.
  952.              */
  953.             (**theList).indent.h = 4;
  954.             GetFontInfo(&info);
  955.             (**theList).indent.v = info.ascent;
  956.             break;
  957.         case lCloseMsg:
  958.             if ((**theList).userHandle != NULL) {
  959.                 ForgetPoly(PRIVATE.openTriangle);
  960.                 ForgetPoly(PRIVATE.closedTriangle);
  961.                 ForgetPoly(PRIVATE.intermediateTriangle);
  962.                 DisposeHandle((**theList).userHandle);
  963.                 (**theList).userHandle = NULL;
  964.             }
  965.             break;
  966.         case lDrawMsg:
  967.             onlyRedrawButton = FALSE;
  968.             if (listDataLen > 0 && (**theList).userHandle != NULL) {
  969.                 /*
  970.                  * Get the cell content. This is the handle that has the list
  971.                  * element. We check that the userHandle has been setup correctly.
  972.                  * Note that we don't use LFind (or similar) because the data
  973.                  * might not be aligned in the list cell storage. Actually, the
  974.                  * data is aligned as we only store Handles in the cells.
  975.                  */
  976.                 cellSize = sizeof twistDownHandle;
  977.                 LGetCell(&twistDownHandle,    &cellSize, listCell, theList);
  978.                 if (cellSize == sizeof twistDownHandle
  979.                  && twistDownHandle != NULL) {
  980.                     elementLockState = HGetState((Handle) twistDownHandle);
  981.                     HLock((Handle) twistDownHandle);
  982.                     twistDownPtr = (*twistDownHandle);
  983.                     onlyRedrawButton = TestFlag(kOnlyRedrawButton);
  984.                     viewRect = *listRect;
  985.                     if (onlyRedrawButton) {
  986.                         if (PRIVATE.isLeftJustify) {
  987.                             viewRect.right = viewRect.left
  988.                                         + (**theList).indent.h
  989.                                         + PRIVATE.triangleWidth;
  990.                         }
  991.                         else {
  992.                             viewRect.left = viewRect.right
  993.                                     - (**theList).indent.h
  994.                                     - kTriangleOutsideGap
  995.                                     - PRIVATE.triangleWidth;
  996.                         }
  997.                     }
  998.                     if (onlyRedrawButton == FALSE || TestFlag(kEraseButtonArea))
  999.                         EraseRect(&viewRect);
  1000.                     if (TestFlag(kHasTwistDown)) {
  1001.                         /*
  1002.                          * Draw the expansion triangle in one of
  1003.                          * its three states.
  1004.                          */
  1005.                         polyPoint.v = listRect->top + 1;
  1006.                         if (PRIVATE.isLeftJustify) {
  1007.                             polyPoint.h = listRect->left
  1008.                                         + (**theList).indent.h
  1009.                                         + kTriangleOutsideGap;
  1010.                         }
  1011.                         else {
  1012.                             polyPoint.h = listRect->right
  1013.                                         - (**theList).indent.h
  1014.                                         - PRIVATE.triangleWidth
  1015.                                         + kTriangleInsideGap;
  1016.                         }
  1017.                         if (TestFlag(kDrawIntermediate))
  1018.                             polyHandle = PRIVATE.intermediateTriangle;
  1019.                         else if (TestFlag(kShowSublist))
  1020.                             polyHandle = PRIVATE.openTriangle;
  1021.                         else {
  1022.                             polyHandle = PRIVATE.closedTriangle;
  1023.                         }
  1024.                         DrawTriangle(
  1025.                             polyHandle,
  1026.                             polyPoint,
  1027.                             TestFlag(kDrawButtonFilled)
  1028.                         );
  1029.                     }
  1030.                     if (onlyRedrawButton == FALSE
  1031.                      && (*twistDownPtr).dataLength > 0) {
  1032.                         /*
  1033.                          * Indent the text to show the depth of the hierarchy.
  1034.                          */
  1035.                         indent = (**theList).indent.h
  1036.                             + PRIVATE.triangleWidth
  1037.                             + (PRIVATE.tabIndent * (*twistDownPtr).indentLevel);
  1038.                         viewRect = *listRect;
  1039.                         /*
  1040.                          * Build a display rectangle for the cell text and set the
  1041.                          * pen to the leftmost position of the text. Note that
  1042.                          * this right-justifies text for Arabic and Hebrew.
  1043.                          */
  1044.                         if (PRIVATE.isLeftJustify)
  1045.                             viewRect.left += indent;
  1046.                         else {
  1047.                             viewRect.right -= indent;
  1048.                         }
  1049.                         GetPort(&grafPtr);
  1050.                         saveFontNumber = grafPtr->txFont;
  1051.                         saveFontSize = grafPtr->txSize;
  1052.                         TextFont(PRIVATE.fontNumber);
  1053.                         TextSize(PRIVATE.fontSize);
  1054.                         if (PRIVATE.drawProc == NULL) {
  1055.                             if (PRIVATE.isLeftJustify) {
  1056.                                 MoveTo(
  1057.                                     viewRect.left,
  1058.                                     viewRect.top + (**theList).indent.v
  1059.                                 );
  1060.                             }
  1061.                             else {
  1062.                                 MoveTo(
  1063.                                     viewRect.right
  1064.                                     - TextWidth(
  1065.                                             (*twistDownPtr).data,
  1066.                                             0,
  1067.                                             (*twistDownPtr).dataLength
  1068.                                         ),
  1069.                                     viewRect.top + (**theList).indent.v
  1070.                                 );
  1071.                             }
  1072.                             DrawText(
  1073.                                 (*twistDownPtr).data,
  1074.                                 0,
  1075.                                 (*twistDownPtr).dataLength
  1076.                             );
  1077.                         }
  1078.                         else {
  1079.                             (PRIVATE.drawProc)(
  1080.                                 theList,
  1081.                                 (const Ptr) (*twistDownPtr).data,
  1082.                                 (*twistDownPtr).dataLength,
  1083.                                 &viewRect
  1084.                             );
  1085.                         }
  1086.                         TextFont(saveFontNumber);
  1087.                         TextSize(saveFontSize);
  1088.                     }                            /* Drawing cell                    */
  1089.                     HSetState((Handle) twistDownHandle, elementLockState);
  1090.                 }                                /* Have list element            */
  1091.             }                                    /* Have cell data                */
  1092.             if (listSelect == FALSE || onlyRedrawButton)
  1093.                 break;
  1094.             /* Continue to do hilite */
  1095.         case lHiliteMsg:
  1096.             if (PRIVATE.canHiliteSelection) {
  1097. #ifdef THINK_C
  1098.                 HiliteMode &= ~(1 << hiliteBit);
  1099. #elif defined(__powerc)
  1100.                 /*
  1101.                  * PowerPC uses accessor functions to test and manipulate
  1102.                  * low-memory variables.
  1103.                  */
  1104.                 LMSetHiliteMode(LMGetHiliteMode() & ~(1 << hiliteBit));
  1105. #else /* MPW */
  1106.                 *((char *) HiliteMode) &= ~(1 << hiliteBit);    /* IM V-61    */
  1107. #endif
  1108.                 viewRect = *listRect;
  1109.                 if (PRIVATE.isLeftJustify)
  1110.                     viewRect.left += ((**theList).indent.h + PRIVATE.triangleWidth);
  1111.                 else {
  1112.                     viewRect.right -= ((**theList).indent.h + PRIVATE.triangleWidth);
  1113.                 }
  1114.                 InvertRect(&viewRect);
  1115.             }
  1116.             break;
  1117.         }
  1118. #undef TestFlag
  1119. }
  1120.  
  1121. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1122.  * DrawTriangle
  1123.  *
  1124.  * DrawTriangle
  1125.  * Draw the polygon and fill it so it looks a bit like the Finder. If isSelected
  1126.  * is TRUE, the polygon is always black-filled. Else, on color monitors, a light
  1127.  * gray is chosen.
  1128.  *
  1129.  * DrawTriangle uses the DeviceLoop available with System 7 to draw across
  1130.  * multiple screens.
  1131.  */
  1132.  
  1133. typedef struct TriangleInfo {
  1134.     PolyHandle                polyHandle;
  1135.     Point                    polyPoint;
  1136. } TriangleInfo, *TriangleInfoPtr;
  1137.  
  1138. static pascal void                DrawThisTriangle(
  1139.         short                        depth,
  1140.         short                        deviceFlags,
  1141.         GDHandle                    targetDevice,
  1142.         TriangleInfoPtr                triangleInfoPtr
  1143.     );
  1144.  
  1145. static void
  1146. DrawTriangle(
  1147.         PolyHandle                    polyHandle,
  1148.         Point                        polyPoint,
  1149.         Boolean                        isSelected
  1150.     )
  1151. {
  1152.         TriangleInfo                triangleInfo;
  1153.         RgnHandle                    drawingRgn;
  1154.         long                        savedA5;
  1155.         
  1156.         /*
  1157.          * Refresh A5 so we can use the QuickDraw globals, even if we're called
  1158.          * in a non-application context.
  1159.          */
  1160.         savedA5 = SetCurrentA5();
  1161.         triangleInfo.polyHandle = polyHandle;
  1162.         triangleInfo.polyPoint = polyPoint;
  1163.         OffsetPoly(polyHandle, polyPoint.h, polyPoint.v);
  1164.         if (isSelected)
  1165.             FillPoly(polyHandle, &qd.black);
  1166.         else {
  1167.             drawingRgn = NewRgn();
  1168.             OpenRgn();
  1169.             FramePoly(polyHandle);
  1170.             CloseRgn(drawingRgn);
  1171. #ifdef powerc
  1172.             /*
  1173.              * To call my private drawing function on PowerPC, we must
  1174.              * create a private routine descriptor. In a production environment,
  1175.              * this would typcally be done once, and myDrawingUPP globalized.
  1176.              */
  1177.             {
  1178.                 DeviceLoopDrawingUPP myDrawingUPP;
  1179.                 
  1180.                 myDrawingUPP = NewDeviceLoopDrawingProc(DrawThisTriangle);
  1181.                 DeviceLoop(
  1182.                     drawingRgn,
  1183.                     myDrawingUPP,
  1184.                     (long) &triangleInfo,
  1185.                     0
  1186.                 );
  1187.                 DisposeRoutineDescriptor(myDrawingUPP);
  1188.             }
  1189. #else
  1190.             DeviceLoop(
  1191.                 drawingRgn,
  1192.                 (DeviceLoopDrawingProcPtr) DrawThisTriangle,
  1193.                 (long) &triangleInfo,
  1194.                 0
  1195.             );
  1196. #endif
  1197.             DisposeRgn(drawingRgn);
  1198.         }
  1199.         /*
  1200.          * A thicker pen might look better for large font sizes, but it needs
  1201.          * to be done in a way that looks good for both button orientations.
  1202.          */
  1203.         FramePoly(polyHandle);
  1204.         OffsetPoly(polyHandle, -polyPoint.h, -polyPoint.v);
  1205.         SetA5(savedA5);
  1206. }
  1207.  
  1208. static pascal void
  1209. DrawThisTriangle(
  1210.         short                        depth,
  1211.         short                        deviceFlags,
  1212.         GDHandle                    targetDevice,
  1213.         TriangleInfoPtr                triangleInfoPtr
  1214.     )
  1215. {
  1216.         RGBColor                    foreColor;
  1217.         RGBColor                    saveForeColor;
  1218.         RGBColor                    backColor;
  1219.         short                        i;
  1220.         Rect                        polyRect;
  1221. #define TRI    (*triangleInfoPtr)
  1222. #pragma unused (deviceFlags, targetDevice)
  1223.  
  1224.         polyRect = (**TRI.polyHandle).polyBBox;
  1225.         LocalToGlobal(& ((Point *) &polyRect)[0]);
  1226.         LocalToGlobal(& ((Point *) &polyRect)[1]);
  1227.         if (depth > 1) {
  1228.             /*
  1229.              * We are drawing on a color device (or devices). Fill the unselected
  1230.              * triangle with a very light gray. The Finder extends this by filling
  1231.              * using the icon color instead of black.
  1232.              */
  1233.             GetForeColor(&foreColor);
  1234.             saveForeColor = foreColor;
  1235.             GetBackColor(&backColor);
  1236. #if 1    /* Normal */
  1237.             /*
  1238.              * This loop sets foreColor to a very light gray.
  1239.              */
  1240.             for (i = 0; i < 8; i++) {
  1241.                 if (GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
  1242.                     break;
  1243.             }
  1244. #else    /* Debug: use a medium gray color so the effect is apparent    */
  1245.             GetGray(GetGDevice(), &backColor, &foreColor);
  1246. #endif
  1247.             RGBForeColor(&foreColor);
  1248.             FillPoly(TRI.polyHandle, &qd.black);
  1249.             RGBForeColor(&saveForeColor);
  1250.         }
  1251.         else {
  1252. #if MONOCHROME_FILL
  1253.             /*
  1254.              * We really don't need to do this, but it was useful in debugging
  1255.              * the algorithm on a machine with multiple displays. This fills
  1256.              * the polygon with a light gray texture on monochrome displays.
  1257.              * This is different from the Finder algorithm.
  1258.              */
  1259.             FillPoly(TRI.polyHandle, (ConstPatternParam) &qd.ltGray);
  1260. #else
  1261.             /*
  1262.              * Normally, we need only erase the interior of the polygon.
  1263.              */
  1264.             ErasePoly(TRI.polyHandle);
  1265. #endif
  1266. #undef TRI
  1267.         }
  1268. }
  1269.